/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.google.code.peersim.pastry.controls;

import com.google.code.peersim.pastry.protocol.PastryId;
import com.google.code.peersim.pastry.protocol.PastryNode;
import com.google.code.peersim.pastry.protocol.PastryProtocolState;
import com.google.code.peersim.pastry.protocol.PastryResourceDiscoveryLsnrIfc;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import peersim.config.Configuration;
import peersim.core.CommonState;
import peersim.core.Control;
import peersim.core.Network;
import peersim.util.FileNameGenerator;
import peersim.util.IncrementalStats;

/**
 * This component is responsible of picking nodes up for asking them to issue
 * reource look-up requests in the pastry network. Resource ids to look for are
 * picked up from the {@link AssignedResources} singleton.
 *
 * @author frusso
 * @version 0.1
 * @since 0.1
 */
public class PastryResourceFinder implements Control, PastryResourceDiscoveryLsnrIfc {

  /**
   * Configures the file this component must log to.
   */
  public static final String LOG_FILE = "log";
  /**
   * Whether log must be enabled or not.
   */
  public static final String DO_LOG = "doLog";

  private PrintStream stream;
  private String fileName;
  private int counter=0;
  private IncrementalStats stats;
  private boolean doLog;

  /**
   * Constructor.
   *
   * @param prefix
   * @throws java.io.FileNotFoundException
   */
  public PastryResourceFinder(String prefix) throws FileNotFoundException {
    doLog = Configuration.getBoolean(prefix + "." + DO_LOG);
    if(doLog) {
      fileName = new FileNameGenerator(Configuration.getString(prefix + "."+LOG_FILE), ".log").nextCounterName();
      stream = new PrintStream(new FileOutputStream(fileName));
    }
    stats = new IncrementalStats();
  }

  /**
   * {@inheritDoc}
   *
   * Randomly selects a {@link PastryNode} in {@link PastryProtocolState#JOINED}
   * states and asks it to start looking for a given resource id.
   */
  @Override
  public boolean execute() {
    boolean stop = false;
    PastryId id = peekResourceId();
    if(id!=null) {
      triggerResourceLookup(id);
    } else {
      // nop, any resource is available
    }
    if(CommonState.getTime()==CommonState.getEndTime()-1) {
      printFinalStats();
    }
    return stop;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void resourceDiscovered(ResourceDiscoveredInfo info) {
    if(doLog) {
      stream.println((++counter)+") "+info.getResourceId() + " " + info.getHops());
    }
    stats.add(info.getHops());
    printStats();
  }

  /**
   * Peeks the first available {@link PastryId} assigned to a resource that has
   * already been assigned to a {@link PastryNode}.
   *
   * @return The id to start looking for
   */
  private PastryId peekResourceId() {
    return AssignedResources.peekAssignedId();
  }

  private void printFinalStats() {
    System.err.println(this.getClass().getName()+"\nAvg-hops: "+stats.getAverage()+"\nMax-hops: "+stats.getMax()+"\nMin-hops: "+stats.getMin()+
      "\nStddev-hops: "+stats.getStD()+"\nVar-hops: "+stats.getVar()+"\nDiscovered resources: "+stats.getN()+"\n");
  }

  private void printStats() {
    if(doLog)
      stream.println("Avg-hops: "+stats.getAverage()+" Max-hops: "+stats.getMax()+" Min-hops: "+stats.getMin()+" Stddev-hops: "+stats.getStD()+
        " Var-hops: "+stats.getVar());
  }

  /**
   * Removes the last {@link PastryId} peeked up calling {@link PastryResourceFinder#peekResourceId()}
   * from the {@link AssignedResources} singleton. This has to be done when the resource with that id
   * has been discovered.
   */
  private void removeResourceId() {
    AssignedResources.remove();
  }

  /**
   * Tells a randomly choosen {@link PastryNode} to start looking for the given
   * {@code id}.
   *
   * @param id The resource id to look for
   */
  private void triggerResourceLookup(PastryId id) {
    if(id != null) {
      PastryNode node = null;
      int dim = Network.size();
      int i = 0;
      do {
        int index = CommonState.r.nextInt(Network.size());
        node = (PastryNode) Network.get(index);
        i++;
      } while (!node.isJoined() && i < dim);

      if(node.isJoined()) {
        removeResourceId();
        node.addResourceDiscoveryListener(this);
        node.lookupResource(id);
      } else {
        // NOP, there are no joined nodes
      }
    }
  }
}